/************************************************************************

								Heroes IV
					  Copyright 2000, The 3DO Company

	------------------------------------------------------------------
  						       scale_bitmap.cpp

	$Header: /game/scale_bitmap.cpp $

	$NoKeywords: $

 ************************************************************************/
// kill the 255+ character identifier warning.  Templates tend to exceed that easily.
#include "precompiled.h"
#pragma warning( disable: 4786 )

#include "scale_bitmap.h"

#include "alpha_blend.h"
#include "bitmap_group.h"
#include "bitmap_layer.h"
#include "memory_bitmap.h"
#include "palette_creator.h"

#include "profile.h"

namespace
{
	struct t_int_pixel
	{
		t_uint32 blue;
		t_uint32 green;
		t_uint32 red;
		t_uint32 alpha;
		t_uint32 weight;
	};
};

int const k_fractional_bits = 10; 
int const k_area_fractional_bits = k_fractional_bits * 2;
int const k_max_delta = 1 << k_fractional_bits;
int const k_area_weight = 1 << k_area_fractional_bits;
int const k_fraction_mask = 0xFFFFFFFF >> (32 - k_fractional_bits);

// -----------------------------------------------------------
// draw a bitmap layer, scaled, to a scan line
// -----------------------------------------------------------
static void draw_scan_line( t_bitmap_layer_24 const& source, int y,
						    t_int_pixel* scan_ptr, int delta, int line_height )
{
	t_bitmap_row const* row = source.get_row( y );
	int                 source_left = source.get_rect().left;
	int                 source_x = row->left + source_left;
	int                 end_x = row->right + source_left;

	if (source_x >= end_x)
		return;

	int           offset = row->offset;
	t_uint8*      source_ptr = source.get_data() + offset;
	t_uint8*      alpha_ptr = 0;
	int           alpha_offset;
	t_pixel_24    pixel;
	int           base_source_area    = delta * line_height;
	int           one_dest_pixel_area = line_height << k_fractional_bits;
	int           source_area;
	int           dest_area = 0;
	int           pixel_area;
	int           pixel_weight;
	int           alpha_depth = source.get_alpha_depth();
	int           alpha = 15;
	t_pixel_24 const* palette = &(*source.get_palette().begin());

	int fraction_x = (source_x * delta) & k_fraction_mask;
	int leading_space = fraction_x * line_height;

	source_area = base_source_area;
	dest_area   = leading_space;

	if (source_area > base_source_area)
		source_area = base_source_area;
	if (alpha_depth == 4)
	{
		alpha_ptr = source.get_alpha_mask() + (offset >> 1);
		alpha_offset = (offset & 1) << 2;
	}
	switch (alpha_depth)
	{
		case 1:
			if (*source_ptr == 0)
				alpha = 0;
			else
				alpha = 15;
			break;
		case 4:
			if (alpha_offset == 0)
			{
				alpha = (*alpha_ptr) & 0xF;
				alpha_offset = 4;
			}
			else
			{
				alpha = (*alpha_ptr++) >> 4;
				alpha_offset = 0;
			}
			break;
	}

	pixel = palette[*source_ptr++];
	while (true)
	{
		if (source_area + dest_area <= one_dest_pixel_area)
			pixel_area = source_area;
		else
			pixel_area = one_dest_pixel_area - dest_area;

		pixel_weight = multiply_alpha16( alpha, pixel_area );
		scan_ptr->red   += pixel.red   * pixel_weight;
		scan_ptr->blue  += pixel.blue  * pixel_weight;
		scan_ptr->green += pixel.green * pixel_weight;
		scan_ptr->weight += pixel_weight;
		scan_ptr->alpha += alpha * pixel_area;
		if ((dest_area += pixel_area) == one_dest_pixel_area)
		{
			scan_ptr++;
			dest_area = 0;
		}
		if ((source_area -= pixel_area) == 0)
		{
			source_x++;
			if (source_x >= end_x)
				return;
			switch (alpha_depth)
			{
				case 1:
					if (*source_ptr == 0)
						alpha = 0;
					else
						alpha = 15;
					break;
				case 4:
					if (alpha_offset == 0)
					{
						alpha = (*alpha_ptr) & 0xF;
						alpha_offset = 4;
					}
					else
					{
						alpha = (*alpha_ptr++) >> 4;
						alpha_offset = 0;
					}
					break;
			}
			pixel = palette[*source_ptr++];
			source_area = base_source_area;
		}
	}
}

// -----------------------------------------------------------
// compute row data
// -----------------------------------------------------------
static void compute_rows( t_bitmap_layer_24 const& source, 
						  std::vector<t_bitmap_row>& dest_rows, 
						  t_screen_rect& dest_rect, int delta )
{
	t_bitmap_row const*      source_row;
	t_screen_rect            source_rect = source.get_rect();
	int                      source_height = source.get_height();
	int                      source_x;
	int                      source_y;
	int                      dest_x;
	t_bitmap_row             dest_row;
	int                      dest_line_height;
	int                      source_line_height;
	int                      line_height;
	int                      dest_width;

	// compute destination size
	int fraction_y = (source_rect.top * delta) & k_fraction_mask;

	dest_width = (source_rect.right * delta + k_max_delta - 1) >> k_fractional_bits;
	dest_rect.left = (source_rect.left * delta) >> k_fractional_bits;
	dest_rect.top  = (source_rect.top * delta) >> k_fractional_bits;
	dest_rect.right = dest_rect.left;

	dest_rows.clear();
	dest_rows.reserve( source_height );

	// start both source and dest at line 0

	source_y = 0;
	dest_line_height = fraction_y;
	source_line_height = delta;
	// set how much of the current line has been drawn
	dest_row.left = dest_width;
	dest_row.right = 0;
	dest_row.offset = 0;

	while ( source_y < source_height )
	{
		// compute how much of the source line will be drawn to the destination
		if (dest_line_height + source_line_height <= k_max_delta)
			line_height = source_line_height;
		else
			line_height = k_max_delta - dest_line_height;

		// update the left bound of the current destination scan line
		source_row = source.get_row( source_y );
		source_x = source_row->left + source_rect.left;
		dest_x = ((source_x * delta) >> k_fractional_bits) - dest_rect.left;
	
		if (source_x < source_row->right + source_rect.left)
		{
			if (dest_x < dest_row.left)
				dest_row.left = dest_x;

			// update the right bound of what has been drawn
			int right = (((source_row->right + source_rect.left) * delta + k_max_delta - 1)
				         >> k_fractional_bits) - dest_rect.left;
			if (right > dest_row.right)
				dest_row.right = right;
		}

		dest_line_height += line_height;
		source_line_height -= line_height;
		if (source_line_height == 0)
		{
			source_y++;
			source_line_height = delta;
		}
		if (dest_line_height == k_max_delta
			|| (source_y == source_height && dest_line_height > 0) )
		{
			if (dest_row.left > dest_row.right)
				dest_row.left = dest_row.right;

			int width = dest_row.right - dest_row.left;

			// insert the calculated row into the array
			dest_rows.push_back( dest_row );
			// update the right edge of the rectangle
			if (dest_row.right > dest_rect.right - dest_rect.left)
				dest_rect.right = dest_row.right + dest_rect.left;

			dest_row.offset += width;
			dest_line_height = 0;
			dest_row.left = dest_width;
			dest_row.right = 0;
		}
	}
	dest_rect.bottom = dest_rect.top + dest_rows.size();
}

inline t_uint16 convert_pixel( t_int_pixel const* source_ptr )
{
	t_pixel_24 pixel;

	// copy pixels, divided by pixel weight, into destination.
	if (source_ptr->weight == k_area_weight) // normal case
	{
		pixel.red   = source_ptr->red >> k_area_fractional_bits;
		pixel.green = source_ptr->green >> k_area_fractional_bits;
		pixel.blue  = source_ptr->blue >> k_area_fractional_bits;
	}
	else if (source_ptr->weight > 0)
	{
		pixel.red   = source_ptr->red / source_ptr->weight;
		pixel.green = source_ptr->green / source_ptr->weight;
		pixel.blue  = source_ptr->blue / source_ptr->weight;
	}
	return convert_to_16_bit( pixel );
}

// -----------------------------------------------------------
// palettize bitmap
// -----------------------------------------------------------
static void convert_line( t_int_pixel const* source_ptr, t_uint16* dest_ptr,
						  t_uint8* alpha_ptr, int count,
				          int alpha_depth, int offset )
{
	t_pixel_24 pixel;

	dest_ptr += offset;
	switch (alpha_depth)
	{
		case 0:
			// copy everything
			while (count--)
			{
				// copy pixels, divided by pixel weight, into destination.
				*dest_ptr++ = convert_pixel( source_ptr++ );
			}
			break;

		case 4:
		{
			// handle odd case at beginning of line
			alpha_ptr += (offset >> 1);
			if (offset & 1)
			{
				if (source_ptr->alpha > 0)
				{
					*dest_ptr = convert_pixel( source_ptr );
				}
				*alpha_ptr |= (source_ptr->alpha >> k_area_fractional_bits) << 4;
				dest_ptr++;
				alpha_ptr++;
				source_ptr++;
				count--;
			}

			int remainder = (count & 1);

			count >>= 1;
			while (count--)
			{
				if (source_ptr->alpha > 0)
					*dest_ptr = convert_pixel( source_ptr );
				*alpha_ptr = (source_ptr->alpha >> k_area_fractional_bits);
				dest_ptr++;
				source_ptr++;
				if (source_ptr->alpha > 0)
					*dest_ptr = convert_pixel( source_ptr );
				*alpha_ptr |= (source_ptr->alpha >> k_area_fractional_bits) << 4;
				alpha_ptr++;
				dest_ptr++;
				source_ptr++;
			}
			// handle even case at end of line
			if (remainder)
			{
				if (source_ptr->alpha > 0)
					*dest_ptr = convert_pixel( source_ptr );
				*alpha_ptr = (source_ptr->alpha >> k_area_fractional_bits);
			}
			break;
		}
	}
}

// -----------------------------------------------------------
// draw a bitmap layer, scaled, to an abstract 32 bit bitmap
// -----------------------------------------------------------
t_bitmap_layer* scale_bitmap_layer( t_bitmap_layer_24 const& source, double ratio )
{
	int                       delta = k_max_delta * ratio;
	std::vector<t_bitmap_row> dest_rows;
	t_pixel_24 const*         palette = &(*source.get_palette().begin());
	std::vector<t_int_pixel>  scan_line;
	t_bitmap_row const*       source_row;
	t_screen_rect             source_rect = source.get_rect();
	int                       source_height = source.get_height();
	int                       source_x;
	int                       source_y;
	int                       dest_x;
	int                       dest_y;
	t_bitmap_row              dest_row;
	int                       dest_line_height;
	int                       source_line_height;
	int                       line_height;
	t_screen_rect             dest_rect;

	compute_rows( source, dest_rows, dest_rect, delta );

	// create the data blocks
	int                      alpha_depth = source.get_alpha_depth();
	t_shared_array<t_uint16> data;
	t_shared_array<t_uint8>  alpha_mask;
	int                      data_size;

	if (dest_rows.size() == 0)
		return new t_bitmap_layer_16( source.get_name(), dest_rect, 0, 0, data, alpha_mask );

	if (alpha_depth == 1)
		alpha_depth = 4;

	// allocate space for one scan line
	scan_line.resize( dest_rect.width() );

	dest_row = dest_rows.back();
	data_size = dest_row.offset + dest_row.right - dest_row.left;
	data = new t_uint16[data_size];
	if (alpha_depth == 4)
		alpha_mask = new t_uint8[(data_size + 1) >> 1];

	// start both source and dest at line 0
	int fraction_y = (source_rect.top * delta) & k_fraction_mask;

	dest_y = 0;
	source_y = 0;
	dest_line_height = fraction_y;
	source_line_height = delta;
	// set how much of the current line has been drawn
	if (dest_rows.size() > 0)
		dest_row = dest_rows[0];
	memset( &(*scan_line.begin()), 0, scan_line.size() * sizeof( t_int_pixel ));

	while ( source_y < source_height )
	{
		// compute how much of the source line will be drawn to the destination
		if (dest_line_height + source_line_height <= k_max_delta)
			line_height = source_line_height;
		else
			line_height = k_max_delta - dest_line_height;

		// update the left bound of the current destination scan line
		source_row = source.get_row( source_y );
		source_x = source_row->left + source_rect.left;
		dest_x = ((source_x * delta) >> k_fractional_bits) - dest_rect.left;
		if (source_x < source_row->right + source_rect.left)
		{
			// draw line
			draw_scan_line( source, source_y, &(*(scan_line.begin() + dest_x)), delta, line_height );
		}

		dest_line_height += line_height;
		source_line_height -= line_height;
		if (source_line_height == 0)
		{
			source_y++;
			source_line_height = delta;
		}
		if (dest_line_height == k_max_delta
			|| (source_y == source_height && dest_line_height > 0) )
		{
			// copy line to destination
			t_int_pixel* source_ptr = &(*(scan_line.begin() + dest_row.left));

			int width = dest_row.right - dest_row.left;

			assert( dest_row.right <= scan_line.size() );
			if (width > 0)
			{
				convert_line( source_ptr,
					          data.get(), alpha_mask.get(), width, alpha_depth, 
							  dest_row.offset );
				memset( source_ptr, 0, width * sizeof( t_int_pixel ));
			}

			// insert the calculated row into the array
			dest_y++;
			if (dest_y < dest_rows.size())
				dest_row = dest_rows[dest_y];
			dest_line_height = 0;
		}
	}
	return new t_bitmap_layer_16( source.get_name(), dest_rect, alpha_depth, &(*dest_rows.begin()), 
		                          data, alpha_mask );
}


// -----------------------------------------------------------
// scale a bitmap group
// -----------------------------------------------------------
void scale_group( t_bitmap_group_24 const& source, t_bitmap_group& dest, double ratio )
{
	t_bitmap_group_24::const_iterator index;

	dest.reserve( source.size() );
	for (index = source.begin(); index != source.end(); index++)
	{
		
		dest.push_back( scale_bitmap_layer( *index, ratio ));
	}
}
